#include <iostream>
#include <math.h>

#include "defs.h"
#include "memPage_t.h"
#include "memPool_t.h"

memPool_t::memPool_t()
	: pageSize(memPool_t::getDefaultPageSize())
	, position(0)
{
	initPages(1);
}

memPool_t::memPool_t(size_t amount)
	: pageSize(memPool_t::getDefaultPageSize())
	, position(0)
{
	initPages(amount ==0 ? 1 : amount);
}

memPool_t::~memPool_t()
{
	memPage_t* page = firstPage;
	while(page != NULL)
	{
		memPage_t* tmpPage = page->getNext();
		delete page;
		page = tmpPage;
	}
}

size_t memPool_t::getPosition() const
{
	return position;
}

bool memPool_t::setPosition(size_t pos)
{
	return setPositionImpl(pos) != NULL;
}

bool memPool_t::isPoolEmpty() const
{
	memPage_t* page = firstPage;
	while(page != NULL)
	{
		if(!page->isPageEmpty())
			return false;
		page = page->getNext();
	}

	return true;
}

size_t memPool_t::getPoolActualSize() const
{
	size_t total=0;

	memPage_t* page = firstPage;
	while(page != NULL)
	{
		total += page->getActualSize();
		page = page->getNext();
	}

	return total;
}

size_t memPool_t::getPoolTotalSize() const
{
	size_t total=0;

	memPage_t* page = firstPage;
	while(page != NULL)
	{
		total+=page->getPageSize();
		page = page->getNext();
	}

	return total;
}

bool memPool_t::read(void* const data, size_t size)
{
	return read(data, size, position);
}

bool memPool_t::read(void* const data, size_t size, size_t pos)
{
	if(pos > getPoolActualSize())
		return false;

	if(pos+size > getPoolTotalSize())
		return false;

	memPage_t* page = setPositionImpl(pos);
	if(page == NULL)
		return false;

	size_t read = 0;
	size_t tmpSize = 0;
	while(read < size)
	{
		tmpSize = page->getPageSize() - page->getPosition();
		tmpSize = tmpSize < size-read? tmpSize : size-read;
		page->read(data, tmpSize, read);
		read += tmpSize;

		page = page->getNext();
		if(page!=NULL)
			page->setPosition(0);

		if(page==NULL && read<size)
			return false;

	}
	setPosition(position + read);

	return true;
}

bool memPool_t::write(const void* const data, size_t size)
{
	return write(data, size, position);
}

bool memPool_t::write(const void* const data, size_t size, size_t pos)
{
	if(!enoughSpaceInPool(size, pos))
		reallocateSpace(size, pos);	

	memPage_t* page = setPositionImpl(pos);
	if(page == NULL)
		return false;

	// now we are sure we have enough space for data

	size_t written = 0;
	size_t tmpSize = 0;
	while(written < size)
	{
		tmpSize = page->getPageSize() - page->getPosition();
		tmpSize = tmpSize < size-written? tmpSize : size-written;
		page->write(data, tmpSize, written);
		written += tmpSize;

		if(page->isPageFull())
		{
			page = page->getNext();
			if(page!=NULL)
				page->setPosition(0);
		}
		if(page == NULL && written<size)
			return false;

	}
	setPosition(position + written);

	return true;
}

void memPool_t::setDefaultPageSize(size_t size)
{
	if(size <= 0)
		return;

	defaultPageSize=size;
}

size_t memPool_t::getDefaultPageSize()
{
	return defaultPageSize;
}

void memPool_t::printInfo()
{
	std::cout<<"# of pages:"<<numOfPages<<std::endl;
	memPage_t* page = firstPage;
	size_t i=0;
	while(page != NULL)
	{
		std::cout<<"page "<<i<<" total size="<<page->getPageSize()<<", actual size="<<page->getActualSize()<<std::endl;
		i++;
		page=page->getNext();
	}
}

void memPool_t::initPages(size_t amount)
{
	numOfPages=amount;

	firstPage = new memPage_t(pageSize);
	memPage_t* tmpPage = firstPage;
	for(size_t i=1; i<numOfPages; i++)
	{
		memPage_t* p = new memPage_t(pageSize);
		p->setPrev(tmpPage);
		tmpPage->setNext(p);
		tmpPage = p;
	}
	lastPage = tmpPage;
}

bool memPool_t::enoughSpaceInPool(size_t size, size_t pos) const
{
	return size+pos <= getPoolTotalSize();
}

void memPool_t::reallocateSpace(size_t size, size_t position)
{
	size_t delta = size + position - getPoolTotalSize();
	size_t amount = (size_t)ceil((float)delta / pageSize);

	memPage_t* tmpPage = lastPage;
	for(size_t i=0; i<amount; i++)
	{
		memPage_t* p = new memPage_t(pageSize);
		p->setPrev(tmpPage);
		p->setNext(NULL);
		tmpPage->setNext(p);
		tmpPage = p;

		numOfPages++;
	}
	lastPage = tmpPage;
}

memPage_t* memPool_t::setPositionImpl(size_t pos)
{
	//set position of pool
	if(pos>getPoolTotalSize())
		return NULL;

	if(pos > getPoolActualSize() && pos>0) //dont allow "holes" by setting position too far
		return NULL;

	// poll position CAN point to poolSize+1 position, but not further
	position = pos;

	//set position of first relevant page
	memPage_t* page = firstPage;
	size_t count = 0;
	while(page != NULL)
		{
		size_t tmpPageSize = page->getPageSize();
		count += tmpPageSize;
		if(pos >= count)
			page = page->getNext();
		else
			{
			page->setPosition(pos - (count - tmpPageSize));
			break;
			}
		}

	return page;
}